Skip to content

Conversation

@SamSyntax
Copy link
Contributor

@SamSyntax SamSyntax commented Nov 1, 2025

Description

Please include a summary of the changes and the related issue. Please also include relevant motivation and context.

Fixes #629 #635 #621
This is a really tiny modification that fixes hydration error mentioned in #629, #635 and #621

CustomThemeProvider just wraps the ThemeProvider from next-themes and uses useState to set mounted state as false initially and then calls useEffect that runs only once on the initial load to set mounted to true. Since it's a client-side hook, it does not run on server so we receive a neutral html from server without theme mismatch. I believe this is a common pattern in working with next-themes, but if anyone knows a better way to handle that I would love to know!

This pr also adds the theme icon change on toggle.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Code refactoring

How Has This Been Tested?

This change has been tested on on both Chromium browser and Zen browser which is using firefox engine under the hood.

Test Configuration:

  • Node version: v23.11.1
  • Browser (if applicable): Chromium, Zen
  • Operating System: Arch Linux

Screenshots (if applicable)

image

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have added screenshots if ui has been changed
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Summary by CodeRabbit

  • New Features

    • Adds a hydration-safe custom theme provider to improve theming reliability on initial load.
  • Bug Fixes

    • Prevents hydration mismatches by deferring theme application until after mount.
  • Style

    • Theme toggle now displays contextual icons (sun in dark mode, moon in light mode).

@netlify
Copy link

netlify bot commented Nov 1, 2025

👷 Deploy request for appcut pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit f8bfbbb

@vercel
Copy link

vercel bot commented Nov 1, 2025

@SamSyntax is attempting to deploy a commit to the OpenCut OSS Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 1, 2025

Walkthrough

Replaces the app ThemeProvider with a new hydration-safe CustomThemeProvider, adds the CustomThemeProvider implementation that defers applying ThemeProvider until client mount, and updates the theme toggle to render Sun or Moon based on current theme.

Changes

Cohort / File(s) Summary
Layout integration
apps/web/src/app/layout.tsx
Swapped import and JSX usage of ThemeProvider to CustomThemeProvider; props forwarded unchanged.
Theme provider implementation
apps/web/src/components/theme-provider.tsx
New CustomThemeProvider component and CustomThemeProviderProps interface; uses useEffect to set mounted state and delays rendering ThemeProvider until after mount to avoid hydration mismatches.
Theme toggle UI
apps/web/src/components/theme-toggle.tsx
Adjusted icon rendering: shows Sun when theme is "dark", otherwise Moon. Click/toggle behavior unchanged; component signature unchanged.

Sequence Diagram

sequenceDiagram
    participant Browser as Browser (SSR/CSR)
    participant Layout as layout.tsx
    participant Custom as CustomThemeProvider
    participant Theme as ThemeProvider

    Browser->>Layout: Receive server-rendered HTML
    Layout->>Custom: Render CustomThemeProvider (mounted=false)
    Custom-->>Browser: Return children only (no ThemeProvider) to match SSR

    Browser->>Custom: Client mount triggers useEffect
    Custom->>Custom: set mounted = true
    Custom->>Theme: Render ThemeProvider with provided props + children
    Theme-->>Browser: Theme context applied on client
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review useEffect mounting logic for edge cases and unnecessary re-renders.
  • Verify theme persistence and first-paint behavior (possible flicker) across browsers.
  • Confirm ThemeToggle icon logic aligns with accessibility labels and styling conventions.

Poem

🐰 I nibbled code where themes take flight,
Waiting to mount before the light.
A gentle wrap, no server fight,
Sun or Moon appears just right. ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix hydration error' is highly specific and directly related to the main change—adding a CustomThemeProvider to resolve hydration mismatches in Next.js theming.
Description check ✅ Passed The description follows the template structure with all major sections completed: issue references, detailed explanation, type of change selected, testing information provided, screenshots included, and comprehensive checklist.
Linked Issues check ✅ Passed The PR addresses the hydration error from #629 by implementing a deferred ThemeProvider pattern, directly solving the server/client mismatch problem reported across the linked issues.
Out of Scope Changes check ✅ Passed All changes are scope-focused: CustomThemeProvider wrapper to fix hydration errors, theme icon toggle change, and layout updates to use the new provider—all directly address the linked issues.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@SamSyntax SamSyntax marked this pull request as ready for review November 1, 2025 13:03
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/app/layout.tsx (1)

38-58: Refactor component tree to ensure useTheme() is only called within ThemeProvider context.

The concern is valid and confirmed. CustomThemeProvider returns unwrapped children before mounting (line 16-17 in theme-provider.tsx), but Toaster uses useTheme() and renders unconditionally within that tree. This causes useTheme() to be called outside its provider context during the initial render, violating hook usage rules and potentially causing runtime warnings or errors.

Move Toaster and Analytics inside StorageProvider, or conditionally render them only after the provider mounts:

{mounted && (
  <>
    <Analytics />
    <Toaster />
  </>
)}

Alternatively, refactor CustomThemeProvider to wrap the entire children tree with a loading boundary until mounted is true.

🧹 Nitpick comments (2)
apps/web/src/components/theme-provider.tsx (2)

6-8: Consider removing redundant children declaration.

ThemeProviderProps from next-themes likely already includes children: React.ReactNode, making this explicit declaration unnecessary.

-interface CustomThemeProviderProps extends ThemeProviderProps {
-  children: React.ReactNode;
-}
+type CustomThemeProviderProps = ThemeProviderProps;

20-20: Remove extra whitespace in closing tag.

There's an extra space before the closing > in </ThemeProvider >.

-  return <ThemeProvider {...props}>{children}</ThemeProvider >
+  return <ThemeProvider {...props}>{children}</ThemeProvider>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bee7aba and b457045.

📒 Files selected for processing (3)
  • apps/web/src/app/layout.tsx (3 hunks)
  • apps/web/src/components/theme-provider.tsx (1 hunks)
  • apps/web/src/components/theme-toggle.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{jsx,tsx}: Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Don't use distracting elements like <marquee> or <blink>.
Only use the scope prop on <th> elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/onBlur.
Include caption tracks for audio and video elements.
Use semantic elements instead of role attributes in JSX.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with...

Files:

  • apps/web/src/components/theme-provider.tsx
  • apps/web/src/app/layout.tsx
  • apps/web/src/components/theme-toggle.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use TypeScript namespaces.
Don't use non-null assertions with the ! postfix operator.
Don't use parameter properties in class constructors.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use either T[] or Array<T> consistently.
Initialize each enum member value explicitly.
Use export type for types.
Use import type for types.
Make sure all enum members are literal values.
Don't use TypeScript const enum.
Don't declare empty interfaces.
Don't let variables evolve into any type through reassignments.
Don't use the any type.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use implicit any type on variable declarations.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use the TypeScript directive @ts-ignore.
Use consistent accessibility modifiers on class properties and methods.
Use function types instead of object types with call signatures.
Don't use void type outside of generic or return types.

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
Use either T[] or Array consistently
Don't use the any type

Files:

  • apps/web/src/components/theme-provider.tsx
  • apps/web/src/app/layout.tsx
  • apps/web/src/components/theme-toggle.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,jsx,ts,tsx}: Don't use the return value of React.render.
Don't use consecutive spaces in regular expression literals.
Don't use the arguments object.
Don't use primitive type aliases or misleading types.
Don't use the comma operator.
Don't write functions that exceed a given Cognitive Complexity score.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names that aren't base 10 or use underscore separators.
Remove redundant terms from logical expressions.
Use while loops instead of...

Files:

  • apps/web/src/components/theme-provider.tsx
  • apps/web/src/app/layout.tsx
  • apps/web/src/components/theme-toggle.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{tsx,jsx}: Always include a title element for icons unless there's text beside the icon
Always include a type attribute for button elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Don't import React itself
Don't define React components inside other components
Don't use both children and dangerouslySetInnerHTML on the same element
Don't insert comments as text nodes
Use <>...</> instead of ...

Files:

  • apps/web/src/components/theme-provider.tsx
  • apps/web/src/app/layout.tsx
  • apps/web/src/components/theme-toggle.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{ts,tsx,js,jsx}: Don't use the comma operator
Use for...of statements instead of Array.forEach
Don't initialize variables to undefined
Use .flatMap() instead of map().flat() when possible
Don't assign a value to itself
Avoid unused imports and variables
Don't use await inside loops
Don't hardcode sensitive data like API keys and tokens
Don't use the delete operator
Don't use global eval()
Use String.slice() instead of String.substr() and String.substring()
Don't use else blocks when the if block breaks early
Put default function parameters and optional function parameters last
Use new when throwing an error
Use String.trimStart() and String.trimEnd() over String.trimLeft() and String.trimRight()

Files:

  • apps/web/src/components/theme-provider.tsx
  • apps/web/src/app/layout.tsx
  • apps/web/src/components/theme-toggle.tsx
🧠 Learnings (4)
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{jsx,tsx} : Use semantic elements instead of role attributes in JSX.

Applied to files:

  • apps/web/src/app/layout.tsx
📚 Learning: 2025-08-15T00:50:09.126Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-08-15T00:50:09.126Z
Learning: Applies to **/*.{tsx,jsx} : Use <>...</> instead of <Fragment>...</Fragment>

Applied to files:

  • apps/web/src/app/layout.tsx
📚 Learning: 2025-08-15T00:50:09.126Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-08-15T00:50:09.126Z
Learning: Applies to **/*.{tsx,jsx} : Always include a title element for icons unless there's text beside the icon

Applied to files:

  • apps/web/src/components/theme-toggle.tsx
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{ts,tsx} : Use consistent accessibility modifiers on class properties and methods.

Applied to files:

  • apps/web/src/components/theme-toggle.tsx
🧬 Code graph analysis (2)
apps/web/src/components/theme-provider.tsx (1)
apps/web/src/components/ui/sonner.tsx (1)
  • props (8-29)
apps/web/src/app/layout.tsx (1)
apps/web/src/components/theme-provider.tsx (1)
  • CustomThemeProvider (10-21)
🔇 Additional comments (2)
apps/web/src/app/layout.tsx (1)

12-12: LGTM: Clean import swap for custom wrapper.

The import correctly replaces the direct next-themes ThemeProvider with the new hydration-safe wrapper.

apps/web/src/components/theme-provider.tsx (1)

1-21: I need to verify a few more things about the actual implementation and what hydration errors are being addressed.

Now let me verify what's actually in the layout file:

No issues found. The implementation correctly follows next-themes documentation.

The next-themes library explicitly recommends delaying rendering any theme toggling UI until mounted on the client, and this component implements exactly that pattern. When using useTheme, a hydration mismatch error occurs because values returned by useTheme are undefined on the server, since localStorage cannot be read until mounting on the client.

The mounted check with useEffect is the standard recommended approach, not a workaround. The component correctly renders children without the ThemeProvider during SSR and wraps them only after client-side hydration completes.

Comment on lines +10 to +18
export default function CustomThemeProvider({ children, ...props }: CustomThemeProviderProps) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);

if (!mounted) {
return <>{children}</>;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Missing ThemeProvider context causes runtime errors for child components.

Returning <>{children}</> without the ThemeProvider wrapper when !mounted (lines 16-18) breaks the theme context. Components like Toaster (which calls useTheme() as shown in the relevant snippet from sonner.tsx) will throw an error during the initial render: "useTheme must be used within a ThemeProvider".

The fix for hydration errors should keep the ThemeProvider wrapped at all times. The suppressHydrationWarning attribute on the <html> tag (already present in layout.tsx line 33) handles theme-related hydration mismatches.

Apply this diff to fix the context issue:

 export default function CustomThemeProvider({ children, ...props }: CustomThemeProviderProps) {
   const [mounted, setMounted] = useState(false);
   useEffect(() => {
     setMounted(true);
   }, []);

-  if (!mounted) {
-    return <>{children}</>;
-  }
-
-  return <ThemeProvider {...props}>{children}</ThemeProvider >
+  return (
+    <ThemeProvider {...props}>
+      {mounted ? children : null}
+    </ThemeProvider>
+  );
 }

Alternatively, if you want to avoid the layout shift from returning null, simply always render children within ThemeProvider and rely on suppressHydrationWarning:

 export default function CustomThemeProvider({ children, ...props }: CustomThemeProviderProps) {
-  const [mounted, setMounted] = useState(false);
-  useEffect(() => {
-    setMounted(true);
-  }, []);
-
-  if (!mounted) {
-    return <>{children}</>;
-  }
-
-  return <ThemeProvider {...props}>{children}</ThemeProvider >
+  return <ThemeProvider {...props}>{children}</ThemeProvider>;
 }

The second approach is simpler and leverages Next.js's built-in hydration handling via suppressHydrationWarning.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web/src/components/theme-provider.tsx around lines 10 to 18, the
component currently returns <>children</> when !mounted which removes the
ThemeProvider and causes runtime errors (e.g., "useTheme must be used within a
ThemeProvider"); remove the early return and always render the ThemeProvider
wrapper around children (i.e., render ThemeProvider regardless of mounted state)
to preserve theme context during initial render and rely on the existing
suppressHydrationWarning in layout.tsx to handle hydration mismatches.

Fix typo in classname
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/components/theme-toggle.tsx (1)

15-25: Add explicit type="button" to the Button element.

The custom Button component does not provide a default type attribute. When rendered as a native button element (the default), it will inherit the browser's default type of "submit", which can cause unintended form submissions. While this button isn't in a form context here, adding an explicit type="button" follows the coding guidelines and prevents potential issues if the component is reused elsewhere.

    <Button
      size="icon"
      variant="text"
      className="h-7"
+     type="button"
      onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
    >
🧹 Nitpick comments (1)
apps/web/src/components/theme-toggle.tsx (1)

28-29: Remove extra trailing newline.

The file has two trailing newlines. Consider keeping only one for consistency with common formatting conventions.

Apply this diff:

   );
 }
-
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e78e9e3 and f8bfbbb.

📒 Files selected for processing (1)
  • apps/web/src/components/theme-toggle.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{jsx,tsx}: Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Don't use distracting elements like <marquee> or <blink>.
Only use the scope prop on <th> elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include a lang attribute on the html element.
Always include a title attribute for iframe elements.
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress.
Accompany onMouseOver/onMouseOut with onFocus/onBlur.
Include caption tracks for audio and video elements.
Use semantic elements instead of role attributes in JSX.
Make sure all anchors are valid and navigable.
Ensure all ARIA properties (aria-*) are valid.
Use valid, non-abstract ARIA roles for elements with...

Files:

  • apps/web/src/components/theme-toggle.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use TypeScript namespaces.
Don't use non-null assertions with the ! postfix operator.
Don't use parameter properties in class constructors.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use either T[] or Array<T> consistently.
Initialize each enum member value explicitly.
Use export type for types.
Use import type for types.
Make sure all enum members are literal values.
Don't use TypeScript const enum.
Don't declare empty interfaces.
Don't let variables evolve into any type through reassignments.
Don't use the any type.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use implicit any type on variable declarations.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use the TypeScript directive @ts-ignore.
Use consistent accessibility modifiers on class properties and methods.
Use function types instead of object types with call signatures.
Don't use void type outside of generic or return types.

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
Use either T[] or Array consistently
Don't use the any type

Files:

  • apps/web/src/components/theme-toggle.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{js,jsx,ts,tsx}: Don't use the return value of React.render.
Don't use consecutive spaces in regular expression literals.
Don't use the arguments object.
Don't use primitive type aliases or misleading types.
Don't use the comma operator.
Don't write functions that exceed a given Cognitive Complexity score.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names that aren't base 10 or use underscore separators.
Remove redundant terms from logical expressions.
Use while loops instead of...

Files:

  • apps/web/src/components/theme-toggle.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{tsx,jsx}: Always include a title element for icons unless there's text beside the icon
Always include a type attribute for button elements
Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Don't import React itself
Don't define React components inside other components
Don't use both children and dangerouslySetInnerHTML on the same element
Don't insert comments as text nodes
Use <>...</> instead of ...

Files:

  • apps/web/src/components/theme-toggle.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{ts,tsx,js,jsx}: Don't use the comma operator
Use for...of statements instead of Array.forEach
Don't initialize variables to undefined
Use .flatMap() instead of map().flat() when possible
Don't assign a value to itself
Avoid unused imports and variables
Don't use await inside loops
Don't hardcode sensitive data like API keys and tokens
Don't use the delete operator
Don't use global eval()
Use String.slice() instead of String.substr() and String.substring()
Don't use else blocks when the if block breaks early
Put default function parameters and optional function parameters last
Use new when throwing an error
Use String.trimStart() and String.trimEnd() over String.trimLeft() and String.trimRight()

Files:

  • apps/web/src/components/theme-toggle.tsx
🧠 Learnings (6)
📚 Learning: 2025-08-15T00:50:09.126Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-08-15T00:50:09.126Z
Learning: Applies to **/*.{tsx,jsx} : Always include a title element for icons unless there's text beside the icon

Applied to files:

  • apps/web/src/components/theme-toggle.tsx
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{jsx,tsx} : Watch out for possible "wrong" semicolons inside JSX elements.

Applied to files:

  • apps/web/src/components/theme-toggle.tsx
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't use constants whose value is the upper-case version of their name.

Applied to files:

  • apps/web/src/components/theme-toggle.tsx
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use standard constants instead of approximated literals.

Applied to files:

  • apps/web/src/components/theme-toggle.tsx
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{ts,tsx} : Don't misuse the non-null assertion operator (!) in TypeScript files.

Applied to files:

  • apps/web/src/components/theme-toggle.tsx
📚 Learning: 2025-08-09T09:03:49.797Z
Learnt from: CR
Repo: OpenCut-app/OpenCut PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-09T09:03:49.797Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't use duplicate class members.

Applied to files:

  • apps/web/src/components/theme-toggle.tsx
🧬 Code graph analysis (1)
apps/web/src/components/theme-toggle.tsx (1)
apps/web/src/components/ui/sonner.tsx (1)
  • props (8-29)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Vercel Agent Review

Comment on lines +21 to +23
{theme === "dark" ? (
<Sun className="!size-[1.1rem]" />
) : (<Moon className="!size-[1.1rem]" />)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Potential hydration mismatch with undefined theme.

The theme value from useTheme() may be undefined during SSR or the first client render (before the CustomThemeProvider mounts), causing the conditional on line 21 to render the Moon icon. Once theme resolves on the client, it might switch to Sun if the theme is "dark", creating a hydration mismatch.

Consider guarding against undefined theme or deferring the render until theme is available.

Apply this diff to add a guard:

-      {theme === "dark" ? (
+      {theme === "dark" ? (
         <Sun className="!size-[1.1rem]" />
-      ) : (<Moon className="!size-[1.1rem]" />)}
+      ) : theme === "light" ? (
+        <Moon className="!size-[1.1rem]" />
+      ) : (
+        <Sun className="!size-[1.1rem]" />
+      )}

Alternatively, consider not rendering any icon until theme is defined:

-      {theme === "dark" ? (
-        <Sun className="!size-[1.1rem]" />
-      ) : (<Moon className="!size-[1.1rem]" />)}
+      {theme === "dark" ? (
+        <Sun className="!size-[1.1rem]" />
+      ) : theme === "light" ? (
+        <Moon className="!size-[1.1rem]" />
+      ) : null}

Run the following script to check if the CustomThemeProvider properly prevents hydration mismatches:


🏁 Script executed:

#!/bin/bash
# Description: Check CustomThemeProvider implementation and ThemeToggle usage

# Check CustomThemeProvider implementation for mounted state
echo "=== CustomThemeProvider implementation ==="
rg -A 20 'export.*CustomThemeProvider' --type tsx

# Check if ThemeToggle guards against undefined theme
echo -e "\n=== ThemeToggle usage of theme ==="
rg -B 2 -A 2 'theme ===' apps/web/src/components/theme-toggle.tsx

Length of output: 448


🏁 Script executed:

#!/bin/bash
# Better search for CustomThemeProvider without problematic type flag
echo "=== CustomThemeProvider implementation ==="
rg -A 20 'export.*CustomThemeProvider' 

# Also check the full theme-toggle.tsx file to see all code
echo -e "\n=== Full theme-toggle.tsx file ==="
cat apps/web/src/components/theme-toggle.tsx

# Check if there's a hooks file or provider file
echo -e "\n=== Looking for theme-related files ==="
fd -e tsx -e ts | grep -i theme | head -20

Length of output: 1736


Address potential hydration mismatch in conditional theme rendering.

The component renders different icons based on the theme value without guarding against undefined. During SSR and before the CustomThemeProvider mounts on the client, useTheme() returns theme: undefined, causing the conditional to render the Moon icon. Once the provider mounts and theme resolves (e.g., to "dark"), the component attempts to render the Sun icon instead, creating a hydration mismatch.

Add a guard to handle the undefined state:

-      {theme === "dark" ? (
+      {theme === "dark" ? (
         <Sun className="!size-[1.1rem]" />
-      ) : (<Moon className="!size-[1.1rem]" />)}
+      ) : theme === "light" ? (
+        <Moon className="!size-[1.1rem]" />
+      ) : null}

Or ensure the component doesn't render until theme is available by checking it within a useEffect or wrapping with a loading state.

🤖 Prompt for AI Agents
In apps/web/src/components/theme-toggle.tsx around lines 21 to 23, the
conditional renders icons directly from theme which can be undefined during SSR
causing hydration mismatches; update the component to guard against undefined by
checking theme before rendering (e.g., return null or a placeholder while theme
=== undefined) or use a mounted flag (set true in useEffect) and only render the
Sun/Moon once theme is defined, ensuring the same markup is produced on server
and client to avoid hydration errors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] can't access property "getDirectory", navigator.storage is undefined

1 participant